using Humanizer;
using RSCore.Files;

namespace RSCore;

public static class TextIO
{
	public static readonly string Symbols = ",./;'[]\\-=<>?:\"{}|_+`~!@#$%^&*() ";

	public static IEnumerable<KeyValuePair<string, string>> EnumerateJson(JsonObject json, string? prefix = null)
	{
		foreach (var (key, child) in json)
		{
			string fullKey = prefix == null ? key : $"{prefix}.{key}";
			if (child is JsonObject obj)
			{
				foreach (var value in EnumerateJson(obj, fullKey))
				{
					yield return value;
				}
			}
			else
			{
				yield return new KeyValuePair<string, string>(fullKey, child.Qstr());
			}
		}
	}

	public static JsonObject ToJson(IEnumerable<KeyValuePair<string, string>> tranlations)
	{
		var json = new JsonObject();
		foreach (var (origKey, text) in tranlations)
		{
			var keys = origKey.Split('.');
			var head = json;
			for (int i = 0; i < keys.Length - 1; i++)
			{
				if (!head.TryGetValue(keys[i], out var val))
				{
					head[keys[i]] = val = new JsonObject();
				}

				if (val.JsonType != JsonType.Object)
				{
					break;
				}
				head = val.Qo();
			}
			head[keys[^1]] = text;
		}
		return json;
	}

	public static RSDirectory ToRSDirectory(string dirName, string modName, IEnumerable<KeyValuePair<string, string>> tranlations)
	{
		var directory = new RSDirectory(dirName);
		foreach (var (key, text) in tranlations)
		{
			if (!key.StartsWith($"Mods.{modName}"))
			{
				continue;
			}
			var data = KeyConverter.Instance.Convert(key);
			if (data.EntryType == EntryType.None)
			{
				var keys = data.Identity.Split('.');
				RSDirectory cur = directory;
				for (var i = 0; i < keys.Length - 1; i++)
				{
					cur = cur.GetOrAddDirectory(keys[i]);
				}
				cur.AddFile(new TextFile(keys[^1])
				{
					Text = text,
					Identity = data.Identity,
				});
			}
			else
			{
				Type type = data.EntryType.GetFileType();
				RSFile Factory()
				{
					return (RSFile)Activator.CreateInstance(type, data.Identity)!;
				}

				var file = directory.GetOrAddDirectory(data.EntryType.ToString().Pluralize())
					.GetOrAddFile(data.Identity, Factory);

				if (type.HasProperty(data.Property, out var info))
				{
					info.SetValue(file, text);
				}
				else if (file is IExtensibleFile extensible)
				{
					extensible.ExtendedProperties.Add(data.Property, text);
				}
				else
				{
					throw new Exception($"未经检查的KeyData，{data.EntryType}中不含有属性{data.Property}");
				}
			}
		}
		return directory;
	}

	public static RSDirectory LoadHjson(string path, string modName)
	{
		return ToRSDirectory(Path.GetFileNameWithoutExtension(path), modName, EnumerateJson(HjsonValue.Load(path).Qo()));
	}

	public static void SaveHjson(string path, RSDirectory text, string modName, bool simplified = true)
	{
		var dirQueue = new Queue<RSDirectory>();
		var dict = new Dictionary<string, string>();
		dirQueue.Enqueue(text);
		while (dirQueue.Count != 0)
		{
			var dir = dirQueue.Dequeue();
			foreach (var child in dir.Directories)
			{
				dirQueue.Enqueue(child);
			}
			foreach (var file in dir.Files.OfType<IKeyConvertable>())
			{
				foreach (var (key, value) in file.Convert())
				{
					dict[KeyConverter.Instance.Convert(key)] = value;
				}
			}
		}
		var json = ToJson(dict);
		if (simplified)
		{
			SimplifyJson(json);
		}
		json = new JsonObject { { "Mods", new JsonObject { { modName, json } } } };
		HjsonValue.Save(json, path, new HjsonOptions() { EmitRootBraces = false, KeepWsc = true });
	}

	public static void WritePackage(string path, RSDirectory package)
	{
		var root = Directory.CreateDirectory(path);
		var build = (BuildFile)package.GetFile("Build");
		using (var stream = File.CreateText(Path.Combine(root.FullName, "Build.txt")))
		{
			build.Write(stream);
		}
		var orig = package.GetDirectory(build.Language);

		foreach (var lang in package.Directories)
		{
			WritePackageInternal(lang, root.CreateSubdirectory(lang.Name), lang == orig ? null : orig);
		}
	}

	private static void WritePackageInternal(RSDirectory dir, DirectoryInfo target, RSDirectory? annotation = null)
	{
		foreach (var file in dir.Files)
		{
			using var writer = File.CreateText(Path.Combine(target.FullName, $"{file.Name}.{file.Extension}.txt"));
			if (annotation != null && annotation.TryGetFile(file.Name, out var val))
			{
				file.Write(writer, val);
			}
			else
			{
				file.Write(writer);
			}
		}

		foreach (var child in dir.Directories)
		{
			var sub = target.CreateSubdirectory(child.Name);
			if (annotation != null && annotation.TryGetDirectory(child.Name, out var val))
			{
				WritePackageInternal(child, sub, val);
			}
			else
			{
				WritePackageInternal(child, sub);
			}
		}
	}

	public static RSDirectory ReadPackage(string path)
	{
		var root = Directory.CreateDirectory(path);
		var package = new RSDirectory(root.Name);
		var build = new BuildFile();
		using (var stream = File.OpenText(Path.Combine(root.FullName, "Build.txt")))
		{
			build.Read(stream);
		}

		foreach (var child in root.EnumerateDirectories())
		{
			package.AddDirectory(ReadPackageInternal(child));
		}
		return package;
	}

	private static RSDirectory ReadPackageInternal(DirectoryInfo info)
	{
		var dir = new RSDirectory(info.Name);
		foreach (var file in info.EnumerateFiles("*.txt", SearchOption.TopDirectoryOnly))
		{
			var fullname = file.Name[..^4];
			var name = Path.GetFileNameWithoutExtension(fullname);
			var ext = Path.GetExtension(fullname);
			var rs = RSFile.Create(name, ext[1..]);
			using var stream = file.OpenText();
			rs.Read(stream);
			dir.AddFile(rs);
		}

		foreach (var child in info.EnumerateDirectories())
		{
			dir.AddDirectory(ReadPackageInternal(child));
		}
		return dir;
	}

	public static JsonObject SimplifyJson(JsonObject root)
	{
		var queue = new Queue<JsonObject>();
		queue.Enqueue(root);
		while (queue.Count != 0)
		{
			var parent = queue.Dequeue();
			foreach (var (key, child) in parent.ToArray())
			{
				if (child is JsonObject obj)
				{
					if (obj.Count == 1)
					{
						var (subKey, subChild) = obj.Single();
						parent.Remove(key);
						if (subChild is JsonObject subObj)
						{
							obj = subObj;
						}
						parent.Add($"{key}.{subKey}", subChild);
					}
					queue.Enqueue(obj);
				}
				else if (child.JsonType == JsonType.String)
				{
					var text = child.Qs();
					if (string.IsNullOrEmpty(text) || text.All(Symbols.Contains))
					{
						parent.Remove(key);
					}
				}
			}
		}
		return root;
	}
}